﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Emulator.GameBoy.Memory;
using Emulator.GameBoyConfig;
using Emulator.GameBoy.GPU;

namespace Emulator.GameBoy.Memory
{
    public class GameboyGPU
    {
        //01010101
        //10101010
        //两个字节数据，上下两位表示一个像素点的颜色索引
        readonly int TileSize = 8 * 2;//每个地址存8位数据，共16KB
        readonly UInt16 LYRegisterAddress = 0xFF44;
        int xGPUMode;

        byte[] xVRAM = new byte[8 * 1024];//8KB video ram
        byte[] xCurrentWinTileMap,
            xCurrentTileData, 
            xCurrentBGTileMap;
        List<GameBoyTile> xBGTileList = new List<GameBoyTile>();

        byte xBGPaletteRegister = 0, 
            xOBA0PaletteRigster=0, 
            xOBA1PaletteRigster=0, 
            xLCDC = 0, 
            xLCDCStatus = 0,
            xLYRegister=0, 
            xInterruptRequestFlag = 0;

        public event Action<UInt16, byte> WriteMemoryRequestEvent;
        public event Action<UInt32[]> FlushFrameFinishEvent = (parameter => { });

        public byte[] VRAM { get { return xVRAM; } }

        public GameboyGPU(GameBoyMemory aMemory)
        {
            WriteMemoryRequestEvent += new Action<ushort, byte>(aMemory.Write8);
            InitialGPUData(aMemory.MemoryData);
        }

        public byte[] Tiles0Data
        {
            get {
            return xVRAM.Skip(0x800).Take(0x1000).ToArray();
        }}

        public byte[] Tiles1Data
        {
            get
            {
                return xVRAM.Take(0x1000).ToArray();
            }
        }

        public byte[] Tiles0Map { get {
            return xVRAM.Skip(0x1800).Take(0x400).ToArray();
        } }

        public byte[] Tiles1Map
        {
            get
            {
                return xVRAM.Skip(0x1C00).Take(0x400).ToArray();
            }
        }

        private byte[] GetSingleTileData(int aTileIndex)
        {
            return xCurrentTileData.Skip(aTileIndex * TileSize).Take(TileSize).ToArray();
        }

        public void FlushFrameData(byte[] aMemoryData, GameBoy.CPU.GameBoyCPU aCPU)
        {
            //double? mElapsedTime = aElapsedTime.TotalMilliseconds - xLastTimeSpan.TotalMilliseconds;
            switch(this.xGPUMode)
            {
                case 2:
                InitialGPUData(aMemoryData);
                xBGTileList.Clear();
                UInt32[] mResult = new UInt32[GameBoyGPUConfig.BGWidth * GameBoyGPUConfig.BGHeight];
                byte[] mFrameColorMaping = new byte[GameBoyGPUConfig.BGWidth * GameBoyGPUConfig.BGHeight];
                //List<UInt32[]> mTileColorDataList = new List<uint[]>();
                for (int tileIndex = 0; tileIndex < xCurrentBGTileMap.Length; tileIndex++)
                {
                    if ((xLCDC & 0x8) == 0x8)
                        xBGTileList.Add(new GameBoyTile(tileIndex, GetColorMap(GetSingleTileData(xCurrentBGTileMap[tileIndex]))));
                    else
                        xBGTileList.Add(new GameBoyTile(tileIndex, GetColorMap(GetSingleTileData((sbyte)xCurrentBGTileMap[tileIndex]))));
                    if (xBGTileList.Count > 32 * 32)
                        throw new Exception("tile count overflow 32*32");
                }

                foreach (GameBoyTile aTile in xBGTileList)
                {
                    aTile.Output(ref mFrameColorMaping, GameBoyGPUConfig.BGWidth);
                }

                for (int index = 0; index < mFrameColorMaping.Length; index++)
                {
                    mResult[index] = GetColorValue(mFrameColorMaping[index]);
                    if (index % GameBoyGPUConfig.BGWidth == 0)
                        WriteMemoryRequestEvent(LYRegisterAddress, (byte)(index / GameBoyGPUConfig.BGWidth));
                }
                WriteMemoryRequestEvent(0xFF0F, (byte)(xInterruptRequestFlag | 0x1));//request v-blank interrupt
                FlushFrameFinishEvent(mResult);
                break;
            }
        }

        public void InitialGPUData(byte[] aMemoryData)
        {
            xLCDC = aMemoryData[0xFF40];
            xLCDCStatus = aMemoryData[0xFF41];
            xLYRegister = aMemoryData[0xFF44];
            xVRAM = aMemoryData.Skip(0x8000).Take(8 * 1024).ToArray();
            xBGPaletteRegister = aMemoryData[0xFF47];
            xOBA0PaletteRigster = aMemoryData[0xFF48];
            xOBA1PaletteRigster = aMemoryData[0xFF49];
            xInterruptRequestFlag = aMemoryData[0xFF0F];

            if ((xLCDC & 0x40) == 0x40)
                xCurrentWinTileMap = Tiles1Map;
            else
                xCurrentWinTileMap = Tiles0Map;
            if ((xLCDC & 0x10) == 0x10)
                xCurrentTileData = Tiles1Data;
            else
                xCurrentTileData = Tiles0Data;

            if ((xLCDC & 0x8) == 0x8)
                xCurrentBGTileMap = Tiles1Map;
            else
                xCurrentBGTileMap = Tiles0Map;
        }

        private byte[] GetColorMap(byte[] aTileData)
        {
            if (aTileData.Length % 2 != 0)
                throw new Exception("VRAM data error");
            byte[] mTileColorMap = new byte[8 * 8];
            int mTilePixelCounter = 0;
            for (int i = 0; i < aTileData.Length; i++)
            {
                uint mLow = aTileData[i];
                ++i;
                uint mHigh = aTileData[i];
                //caculate the color index
                for (int j = 0; j < 8; j++)//Bit 0 is the leftmost pixel, and Bit 7 the rightmost.
                {
                    int mHighValue = ((mHigh & (1 << j)) == (1 << j)) ? 1 : 0;
                    int mLowValue = ((mLow & (1 << j)) == (1 << j)) ? 1 : 0;
                    mTileColorMap[mTilePixelCounter] = (byte)((mHighValue *2) + mLowValue);
                    if (mTilePixelCounter >= 8 * 8)
                        throw new Exception("Color data error");
                    mTilePixelCounter++;
                }
            }
            return mTileColorMap;
        }

        //private UInt32[] GetColorData(byte[] aTileData)
        //{
        //    if (aTileData.Length % 2 != 0)
        //        throw new Exception("VRAM data error");
        //    UInt32[] mTileColorData = new UInt32[8*8];
        //    int mTilePixelCounter=0;
        //    for (int i = 0; i < aTileData.Length; i++)
        //    {
        //        uint mLow =  aTileData[i];
        //        ++i;
        //        uint mHigh =  aTileData[i];
        //        //caculate the color index
        //        for (int j = 0; j <8; j++)//Bit 0 is the leftmost pixel, and Bit 7 the rightmost.
        //        {
        //            int mHighValue = ((mHigh & (1<<j))==(1<<j))?1:0;
        //            int mLowValue = ((mLow & (1 << j)) == (1 << j)) ? 1 : 0;
        //            mTileColorData[mTilePixelCounter] = GetColorValue(((mHighValue<<1)+ mLowValue));
        //            if (mTilePixelCounter >= 8*8)
        //                throw new Exception("Color data error");
        //            mTilePixelCounter++;
        //        }
        //    }
        //    return mTileColorData;
        //}

        private UInt32 GetColorValue(int aColorIndex)
        {
            switch (aColorIndex)
            {
                case 0:
                    return ColorConfig.ColorMap[xBGPaletteRegister&3];
                case 1:
                    return ColorConfig.ColorMap[(xBGPaletteRegister&12)>>2];
                case 2:
                    return ColorConfig.ColorMap[(xBGPaletteRegister&48)>>4];
                case 3:
                    return ColorConfig.ColorMap[(xBGPaletteRegister&192) >> 6];
                default:
                    throw new Exception("out of color index");
            }
        }

#if DEBUG
        public string OutputVRAMString()
        {
            StringBuilder mSB = new StringBuilder();
            int mIndexCounter = 8;
            int mVRAMBase = 0x8000;
            foreach(byte mVRAM in xVRAM)
            {
                if (mIndexCounter % 8 == 0)
                {
                    mSB.Append(Convert.ToString(mVRAMBase, 16).PadLeft(4, '0'));
                    mSB.Append(":");
                }
                mSB.Append(Convert.ToString(mVRAM, 16).PadLeft(0, '2'));
                if (mIndexCounter == 1)
                {
                    mSB.Append("\r\n");
                    mIndexCounter = 8;
                }
                else
                {
                    mSB.Append(" ");
                    mIndexCounter--;
                }
                mVRAMBase++;
            }
            return mSB.ToString();
        }
#endif
    }
}
